///\todo upgrade

//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2011 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef _BUOLA_IO_CBINARYWRITER_H_
#define _BUOLA_IO_CBINARYWRITER_H_

#include <buola/io/cbufferstream.h>

namespace buola { namespace io {

///\addtogroup io
///@{

class CBinaryWriter
{
public:
    CBinaryWriter(io::PBufferStream pStream,EEndianness pEndianness=EEndianness::SYSTEM)
        :   mStream(std::move(pStream))
        ,   mEndianness(pEndianness)
    {}

    CBinaryWriter(const CURI &pURI,EMode pMode=EMode::WRITE,EEndianness pEndianness=EEndianness::SYSTEM);

    CResult Write(const uint8_t *pData,size_t pSize)
    {
        return mStream->Write(pData,pSize);
    }
    
    CResult Read(uint8_t *pData,size_t pSize)
    {
        return mStream->Read(pData,pSize);
    }
    
    template<typename tInt>
    void WriteInteger(tInt pInt,EEndianness pByteOrder,std::size_t pLen)
    {
        const uint8_t *lSrc=reinterpret_cast<const uint8_t*>(&pInt);

        if(pByteOrder==EEndianness::LITTLE)
        {
            if(pLen<=sizeof(tInt))
            {
                Write(lSrc,pLen);
            }
            else
            {
                Write(lSrc,sizeof(tInt));

                uint8_t lBuf[pLen-sizeof(tInt)];
                uint8_t lFill=(is_negative(pInt))?0xff:0x00;
                for(int i=0;i<pLen-sizeof(tInt);i++)
                    lBuf[i]=lFill;
                Write(lBuf,pLen-sizeof(tInt));
            }
        }
        else
        {
            if(pLen<=sizeof(tInt))
            {
                for(int i=pLen-1;i>=0;i--)
                    Write(lSrc+i,1);
            }
            else
            {
                uint8_t lBuf[pLen-sizeof(tInt)];
                uint8_t lFill=(is_negative(pInt))?0xff:0x00;
                for(int i=0;i<pLen-sizeof(tInt);i++)
                    lBuf[i]=lFill;
                Write(lBuf,pLen-sizeof(tInt));

                for(int i=sizeof(tInt)-1;i>=0;i--)
                    Write(lSrc+i,1);
            }
        }
    }
    
    template<typename tInt>
    void WriteInteger(tInt pInt)
    {
        WriteInteger(pInt,mEndianness,sizeof(pInt));
    }
    
    template<typename tFloat>
    void WriteReal(tFloat pFloat,EEndianness pByteOrder)
    {
        const uint8_t *lSrc=reinterpret_cast<const uint8_t*>(&pFloat);

        if(pByteOrder==EEndianness::LITTLE)
        {
            Write(lSrc,sizeof(pFloat));
        }
        else
        {
            uint8_t lBuf[sizeof(pFloat)];
            for(int i=0;i<sizeof(pFloat);i++)
                lBuf[i]=lSrc[sizeof(pFloat)-i-1];
            Write(lBuf,sizeof(pFloat));
        }
    }

    template<typename tFloat>
    void WriteReal(tFloat pFloat)
    {
        WriteReal(pFloat,mEndianness);
    }
    
    template<typename tInt,OEnableIf<std::is_integral<tInt>> vDummy=1>
    CBinaryWriter &operator<<(tInt pInt)
    {
        WriteInteger(pInt);
        return *this;
    }
    
    template<typename tFloat,OEnableIf<std::is_floating_point<tFloat>> vDummy=1>
    CBinaryWriter &operator<<(tFloat pFloat)
    {
        WriteReal(pFloat);
        return *this;
    }
    
    const PBufferStream &Stream()   {   return mStream; }

private:
    PBufferStream mStream;
    EEndianness mEndianness;
};

template<typename tType>
CBinaryWriter &operator<<(CBinaryWriter &pS,const std::vector<tType> &pV)
{
    pS << pV.size();
    for(const auto &e : pV)
        pS << e;
    return pS;
}

template<typename tType>
auto operator<<(CBinaryWriter &pS,const tType &pType) -> OFirst<CBinaryWriter&,decltype(serialize(pS,pType))>
{
    serialize(pS,pType);
    return pS;
}

///@}

/*namespace io*/ } /*namespace buola*/ }

#endif
